#include "tcp_connection.hh"

#include <algorithm>
#include <iostream>

using namespace std;

bool TCPConnection::end_prerequisites() {
    return _receiver.stream_out().input_ended() && _sender.stream_in().eof() && (_sender.bytes_in_flight() == 0);
}

void TCPConnection::send_rst() {
    if (_sender.segments_out().empty()) {
        _sender.send_empty_segment();
    }
    auto seg = _sender.segments_out().front();
    seg.header().rst = true;
    _segments_out.push(seg);
    _sender.segments_out().pop();
    set_inactive();
}

void TCPConnection::set_inactive() {
    inbound_stream().set_error();
    _sender.stream_in().set_error();
    _active = false;
}

void TCPConnection::send_segments() {
    // SYN segment not acked yet
    if (!_receiver.ackno().has_value() && !_sender.segments_out().empty()) {
        auto seg = _sender.segments_out().front();
        _segments_out.push(seg);
        _sender.segments_out().pop();
        return;
    }
    while (!_sender.segments_out().empty()) {
        auto seg = _sender.segments_out().front();
        seg.header().ack = true;
        seg.header().ackno = _receiver.ackno().value();
        seg.header().win = static_cast<uint16_t>(_receiver.window_size());
        _segments_out.push(seg);
        _sender.segments_out().pop();
    }
}

void TCPConnection::connect() {
    _sender.fill_window();
    send_segments();
}

size_t TCPConnection::write(const string &data) {
    size_t bytes_written = _sender.stream_in().write(data);
    _sender.fill_window();
    send_segments();
    return bytes_written;
}

size_t TCPConnection::remaining_outbound_capacity() const { return _sender.stream_in().remaining_capacity(); }

void TCPConnection::end_input_stream() { 
    _sender.stream_in().end_input();
    _sender.fill_window();
    send_segments();
}

size_t TCPConnection::bytes_in_flight() const { return {_sender.bytes_in_flight()}; }

size_t TCPConnection::unassembled_bytes() const { return {_receiver.unassembled_bytes()}; }

size_t TCPConnection::time_since_last_segment_received() const { return _time_passed; }

void TCPConnection::segment_received(const TCPSegment &seg) {
    if (seg.header().rst) {
        set_inactive();
        return;
    }

    if (!_receiver.ackno().has_value() && !seg.header().syn) {
        return;
    }
    _time_passed = 0;
    
    if (seg.header().fin && !_sender.stream_in().input_ended()) {
        _linger_after_streams_finish = false;
    }

    if (seg.header().ack) {
        _sender.ack_received(seg.header().ackno, seg.header().win);
        if (!_linger_after_streams_finish && end_prerequisites()) {
            _active = false;
        }
    }

    _receiver.segment_received(seg);

    _sender.fill_window();

    // must send ack and new window size if there is new data received
    if (_sender.segments_out().empty() && (seg.length_in_sequence_space() != 0)) {
        // create an empty segment if there is not any
        _sender.send_empty_segment();
    }
    send_segments();

    // special case: reply to "keep-alive" segments
    if (_receiver.ackno().has_value() && (seg.length_in_sequence_space() == 0) &&
        (seg.header().seqno == _receiver.ackno().value() - 1)) {
        _sender.send_empty_segment();
        send_segments();
    }
}

//! \param[in] ms_since_last_tick number of milliseconds since the last call to this method
void TCPConnection::tick(const size_t ms_since_last_tick) { 
    _time_passed += ms_since_last_tick;
    //cout << "_time_passed: " << _time_passed << "\n";
    _sender.tick(ms_since_last_tick);
    send_segments(); 
    if (_sender.consecutive_retransmissions() > _cfg.MAX_RETX_ATTEMPTS) {
        _segments_out.pop();
        send_rst();
    }
    if (end_prerequisites() && _time_passed >= (10 * static_cast<size_t>(_cfg.rt_timeout))) {
        _active = false;
    }
}

bool TCPConnection::active() const { return {_active}; }

TCPConnection::~TCPConnection() {
    try {
        if (active()) {
            cerr << "Warning: Unclean shutdown of TCPConnection\n";
            send_rst();
        }
    } catch (const exception &e) {
        std::cerr << "Exception destructing TCP FSM: " << e.what() << std::endl;
    }
}
